package net.northcoding.fresh.ast;
// @Author Diamond - Administrator
// @Date 2023/12/8 11:18:00

import net.northcoding.fresh.eval.Eval;
import net.northcoding.fresh.syntax.SyntaxBuilder;
import net.northcoding.fresh.token.Token;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Vector;

public class Expression extends AstNode {
    public static final Map<ExpressionType, SyntaxBuilder> syntaxes = new HashMap<>();
    private static final Map<ExpressionType, Eval> evalers = new HashMap<>();
    private static final Map<ExpressionType, Integer> level = new HashMap<>();

    static {
        syntaxes.put(ExpressionType.PLUS, Expression::infix);
        syntaxes.put(ExpressionType.SUB, Expression::infix);
        syntaxes.put(ExpressionType.MUL, Expression::infix);
        syntaxes.put(ExpressionType.DIV, Expression::infix);
        syntaxes.put(ExpressionType.MOD, Expression::infix);
        syntaxes.put(ExpressionType.INT, (tokens) -> new Expression(tokens.get(0)));
        syntaxes.put(ExpressionType.STR, (tokens) -> new Expression(tokens.get(0)));
    }

    static {
        evalers.put(ExpressionType.PLUS, (node) -> (Integer) (node.getChildren().get(0).exec()) + (Integer) (node.getChildren().get(1).exec()));
        evalers.put(ExpressionType.SUB, (node) -> (Integer) (node.getChildren().get(0).exec()) - (Integer) (node.getChildren().get(1).exec()));
        evalers.put(ExpressionType.MUL, (node) -> (Integer) (node.getChildren().get(0).exec()) * (Integer) (node.getChildren().get(1).exec()));
        evalers.put(ExpressionType.DIV, (node) -> (Integer) (node.getChildren().get(0).exec()) / (Integer) (node.getChildren().get(1).exec()));
        evalers.put(ExpressionType.MOD, (node) -> (Integer) (node.getChildren().get(0).exec()) % (Integer) (node.getChildren().get(1).exec()));
        evalers.put(ExpressionType.INT, (node) -> Integer.parseInt(node.getValue().getLiteral()));
    }

    static {
        level.put(ExpressionType.VAR, 1);
        level.put(ExpressionType.FOR, 0);
        level.put(ExpressionType.PLUS, 4);
        level.put(ExpressionType.SUB, 4);
        level.put(ExpressionType.SETVAR, 3);
        level.put(ExpressionType.MUL, 5);
        level.put(ExpressionType.DIV, 5);
        level.put(ExpressionType.MOD, 5);
        level.put(ExpressionType.INT, 10);
    }

    public Expression() {
    }

    public Expression(Token token) {
        this.value = token;
    }

    private static Expression infix(Vector<Token> tokens) {
        Expression result = new Expression();
        int i = getPos(tokens);
        result.addChild
                (Objects.requireNonNull(
                                parse(
                                        new Vector<>(tokens.subList(0, i)))
                        )
                );
        result.addChild(
                Objects.requireNonNull(
                        parse(
                                new Vector<>(tokens.subList(i + 1, tokens.size()))
                        )
                )
        );
        result.value = tokens.get(i);
        return result;
    }

    public static AstNode parse(Vector<Token> tokens) {
        if (tokens.isEmpty()) {
            try {
                logger.error("Unexpected token");
                throw new RuntimeException("Syntax Error");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
        if (tokens.get(0).getType() == Token.TokenType.OP && tokens.get(tokens.size() - 1).getType() == Token.TokenType.CP) {
            return parse(new Vector<>(tokens.subList(1, tokens.size() - 1)));
        }
        SyntaxBuilder syntaxBuilder = syntaxes.get(getType(tokens));
        return syntaxBuilder.build(tokens);
    }

    private static ExpressionType _getType(Token token) throws IllegalArgumentException {
        switch (token.getType()) {
            case INT -> {
                return ExpressionType.INT;
            }
            case PLUS -> {
                return ExpressionType.PLUS;
            }
            case SUB -> {
                return ExpressionType.SUB;
            }
            case MUL -> {
                return ExpressionType.MUL;
            }
            case DIV -> {
                return ExpressionType.DIV;
            }
            case PERCENT -> {
                return ExpressionType.MOD;
            }
            case EQU -> {
                return ExpressionType.SETVAR;
            }
            case EOF -> {
                return null;
            }
            default -> {
                switch (token.getLiteral()) {
                    case "var" -> {
                        return ExpressionType.VAR;
                    }
                    case "if" -> {
                        return ExpressionType.IF;
                    }
                }
            }
        }
        return ExpressionType.IDENTITY;
//        throw new IllegalArgumentException();
    }

    public static ExpressionType getType(Vector<Token> tokens) {
        return _getType(tokens.get(getPos(tokens)));
    }

    private static int getPos(Vector<Token> tokens) {
        int i = -1, pos = -1;
        int rec = 10, p = 0;
        Token rt = new Token();// var a = 0;
        for (Token token : tokens) {
            ++i;
            if (p == -1) {
                try {
                    logger.error(String.format("Unexpected token %s", token.getLiteral()));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (token.getType() == Token.TokenType.OP) {
                p++;
                continue;
            }
            if (token.getType() == Token.TokenType.CP) {
                p--;
                continue;
            }
            int l = level.get(_getType(token)) + p;
            if (l <= rec) {
                rec = l;
                pos = i;
            }
        }
        if (p != 0) {
            try {
                logger.error("Unexpected token");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            throw new RuntimeException("Unexpected Param");
        }
        return pos;
    }

    private ExpressionType getType() {
        return _getType(this.value);
    }

    @Override
    public Object exec() {
        return evalers.get(this.getType()).eval(this);
    }


    public enum ExpressionType {
        PLUS, SUB, MUL, DIV, MOD, INT, VAR, STR, FOR, WHILE, IF, SETVAR, IDENTITY,
    }

}
